Skip to content
标签
缓存
字数
3695 字
阅读时间
22 分钟

配置类

java

import com.commnetsoft.core.cache.extend.CommnetFastJsonRedisSerializer;
import com.commnetsoft.core.cache.extend.CommnetRedisCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;


/**
 * 缓存配置类
 *
 * @author Brack.zhu
 * @date 2020/4/8
 */
@Configuration
public class CacheConfig  {

    private CommnetFastJsonRedisSerializer commnetFastJsonRedisSerializer=new CommnetFastJsonRedisSerializer();

    /**
     * Spring Cache Redis配置<br/>
     * <ou>
     * <li>序列化使用FastJson</li>
     * <ou/>
     *
     * @param redisConnectionFactory
     * @return
     */
    @Bean
    public RedisCacheManager custCacheManager(RedisConnectionFactory redisConnectionFactory) {
        //包装成SerializationPair类型
        RedisSerializationContext.SerializationPair serializationPair = RedisSerializationContext.SerializationPair.fromSerializer(commnetFastJsonRedisSerializer);
        //redis默认配置文件
        //统一设置过期时间
        //.entryTtl(Duration.ofDays(1));
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
        //设置序列化器
        redisCacheConfiguration = redisCacheConfiguration.serializeValuesWith(serializationPair);
        //扩展 RedisCacheManager 生成器创建
        CommnetRedisCacheManager.CommnetRedisCacheManagerBuilder builder=CommnetRedisCacheManager.commnetBuilder(redisConnectionFactory).cacheDefaults(redisCacheConfiguration);
        return builder.build();
    }


    /**
     * RedisTemplate 序列化使用用FastJson
     *
     * @param redisConnectionFactory
     * @return
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        //设置默认的Serialize,包含 valueSerializer
        redisTemplate.setDefaultSerializer(commnetFastJsonRedisSerializer);
        redisTemplate.setKeySerializer(StringRedisSerializer.UTF_8);
        return redisTemplate;
    }

    /**
     * 为fastJson 反序列化创建类白名单,因为fastjson @type自动类型在类没有序列化就直接反序列化是禁止的,
     * 所以将SpringCache注解的返回类增加到白名单。<br/>
     *  第一次保存和使用缓存对象:序列化 反序列化  操作正常<br/>>
     *   服务重启后使用缓存对象:出现autoTypeSupport异常<br/>>
     *    该方法解决这个异常问题
     * @param ae
     */
    public static void fastjsonAddAccept(AnnotatedElement ae){
        if(ae instanceof Method){
            Method  aeMethod=(Method)ae;
            Class<?> returnClazz=aeMethod.getReturnType();
            CommnetFastJsonRedisSerializer.getDefaultRedisConfig().addAccept(returnClazz.getName());
        }
    }


    public CommnetFastJsonRedisSerializer getCommnetFastJsonRedisSerializer() {
        return commnetFastJsonRedisSerializer;
    }

}

fastjson序列化

java

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.util.IOUtils;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;

/**
 * Redis FastJson对象序列化处理器<br/>
 * 修改点:将defaultRedisConfig对象开放外部访问方法
 * 参照实现类 {@link  com.alibaba.fastjson.support.spring.GenericFastJsonRedisSerializer}
 * @author Brack.zhu
 * @date 2020/5/13
 */
public class CommnetFastJsonRedisSerializer implements RedisSerializer<Object> {

    private final static ParserConfig defaultRedisConfig = new ParserConfig();

    static {
        defaultRedisConfig.setAutoTypeSupport(true);
    }

    @Override
    public byte[] serialize(Object object) throws SerializationException {
        if (object == null) {
            return new byte[0];
        }
        try {
            return JSON.toJSONBytes(object, SerializerFeature.WriteClassName);
        } catch (Exception ex) {
            throw new SerializationException("Could not serialize: " + ex.getMessage(), ex);
        }
    }

    @Override
    public Object deserialize(byte[] bytes) throws SerializationException {
        if (bytes == null || bytes.length == 0) {
            return null;
        }
        try {
            return JSON.parseObject(new String(bytes, IOUtils.UTF8), Object.class, defaultRedisConfig);
        } catch (Exception ex) {
            throw new SerializationException("Could not deserialize: " + ex.getMessage(), ex);
        }
    }

    public static ParserConfig getDefaultRedisConfig() {
        return defaultRedisConfig;
    }
}

自定义缓存(支持自定义有效期)

java

import org.springframework.cache.support.SimpleValueWrapper;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.redis.cache.RedisCache;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheWriter;

import java.time.Duration;


/**
 * 自定义RedisCache<br/>
 *   支持缓存自定义有效期
 * @author Brack.zhu
 * @date 2020/4/8
 */
public class CommnetRedisCache extends RedisCache {

    private final String name;
    private final RedisCacheWriter cacheWriter;
    private final RedisCacheConfiguration cacheConfig;
    private final ConversionService conversionService;

    /**
     * Create new {@link RedisCache}.
     *
     * @param name        must not be {@literal null}.
     * @param cacheWriter must not be {@literal null}.
     * @param cacheConfig must not be {@literal null}.
     */
    protected CommnetRedisCache(String name, RedisCacheWriter cacheWriter, RedisCacheConfiguration cacheConfig) {
        super(name, cacheWriter, cacheConfig);
        this.name = name;
        this.cacheWriter = cacheWriter;
        this.cacheConfig = cacheConfig;
        this.conversionService = cacheConfig.getConversionService();
    }

    private byte[] createAndConvertCacheKey(Object key) {
        return serializeCacheKey(createCacheKey(key));
    }

    @Override
    public void put(Object key, Object value) {
        Object cacheValue = preProcessCacheValue(value);

        if (!isAllowNullValues() && cacheValue == null) {

            throw new IllegalArgumentException(String.format(
                    "Cache '%s' does not allow 'null' values. Avoid storing null via '@Cacheable(unless=\"#result == null\")' or configure RedisCache to allow 'null' via RedisCacheConfiguration.",
                    name));
        }
        cacheWriter.put(name, createAndConvertCacheKey(key), serializeCacheValue(cacheValue), cacheTTL(key));
    }


    @Override
    public ValueWrapper putIfAbsent(Object key, Object value) {
        Object cacheValue = preProcessCacheValue(value);

        if (!isAllowNullValues() && cacheValue == null) {
            return get(key);
        }

        byte[] result = cacheWriter.putIfAbsent(name, createAndConvertCacheKey(key), serializeCacheValue(cacheValue),
                cacheTTL(key));

        if (result == null) {
            return null;
        }

        return new SimpleValueWrapper(fromStoreValue(deserializeCacheValue(result)));
    }

    /**
     * 扩展---根据key获取对应缓存 超时时间
     * @param key
     * @return
     */
    private Duration cacheTTL(Object key){
        //超时时间改造
        Long duration=CommnetRedisCacheManager.getCacheDuration();
        if(null!=duration){
            return Duration.ofMillis(duration);
        }else{
            return cacheConfig.getTtl();
        }
    }

}

自定义RedisCacheManager

java

import com.commnetsoft.commons.utils.ThreadLocalUtils;
import org.springframework.data.redis.cache.*;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

/**
 *  自定义RedisCacheManager
 * @author Brack.zhu
 * @date 2020/4/8
 */
public class CommnetRedisCacheManager extends RedisCacheManager {

    private final RedisCacheWriter cacheWriter;

    private final RedisCacheConfiguration defaultCacheConfig;

    public CommnetRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) {
        super(cacheWriter, defaultCacheConfiguration);
        this.cacheWriter = cacheWriter;
        this.defaultCacheConfig = defaultCacheConfiguration;
    }

    public CommnetRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, String... initialCacheNames) {
        super(cacheWriter, defaultCacheConfiguration, initialCacheNames);
        this.cacheWriter = cacheWriter;
        this.defaultCacheConfig = defaultCacheConfiguration;
    }

    public CommnetRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, boolean allowInFlightCacheCreation, String... initialCacheNames) {
        super(cacheWriter, defaultCacheConfiguration, allowInFlightCacheCreation, initialCacheNames);
        this.cacheWriter = cacheWriter;
        this.defaultCacheConfig = defaultCacheConfiguration;
    }

    public CommnetRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, Map<String, RedisCacheConfiguration> initialCacheConfigurations) {
        super(cacheWriter, defaultCacheConfiguration, initialCacheConfigurations);
        this.cacheWriter = cacheWriter;
        this.defaultCacheConfig = defaultCacheConfiguration;
    }

    public CommnetRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, Map<String, RedisCacheConfiguration> initialCacheConfigurations, boolean allowInFlightCacheCreation) {
        super(cacheWriter, defaultCacheConfiguration, initialCacheConfigurations, allowInFlightCacheCreation);
        this.cacheWriter = cacheWriter;
        this.defaultCacheConfig = defaultCacheConfiguration;
    }

    /*---------------------------扩展增加 start-----------------------------------*/

    /**
     * 缓存超时时间 ThreadLocal key
     */
    public static  final  String CACHE_DURATION_KEY="Cache_Duration_Key";

    /**
     * 增加缓存超时时间 线程局部变量
     * @param duration
     */
    public static void addCacheDuration(long duration) {
        ThreadLocalUtils.set(CACHE_DURATION_KEY,duration);
    }

    /**
     * 清除缓存超时时间 线程局部变量
     */
    public static void clearCacheDuration() {
        ThreadLocalUtils.del(CACHE_DURATION_KEY);
    }

    /**
     * 获取缓存超时时间 线程局部变量
     * @return
     */
    public  static Long getCacheDuration(){
        return ThreadLocalUtils.get(CACHE_DURATION_KEY);
    }

    /*---------------------------扩展增加 end-----------------------------------*/

    @Override
    protected RedisCache createRedisCache(String name, @Nullable RedisCacheConfiguration cacheConfig) {
        return new CommnetRedisCache(name, cacheWriter, cacheConfig != null ? cacheConfig : defaultCacheConfig);
    }

    /**
     * Entry point for builder style {@link RedisCacheManager} configuration.
     *
     * @param connectionFactory must not be {@literal null}.
     * @return new {@link RedisCacheManagerBuilder}.
     */
    public static CommnetRedisCacheManagerBuilder commnetBuilder(RedisConnectionFactory connectionFactory) {

        Assert.notNull(connectionFactory, "ConnectionFactory must not be null!");

        return CommnetRedisCacheManagerBuilder.fromConnectionFactory(connectionFactory);
    }


    /**
     * Entry point for builder style {@link RedisCacheManager} configuration.
     *
     * @param cacheWriter must not be {@literal null}.
     * @return new {@link RedisCacheManagerBuilder}.
     */
    public static CommnetRedisCacheManagerBuilder commnetBuilder(RedisCacheWriter cacheWriter) {

        Assert.notNull(cacheWriter, "CacheWriter must not be null!");

        return CommnetRedisCacheManagerBuilder.fromCacheWriter(cacheWriter);
    }


    /**
     * Configurator for creating {@link RedisCacheManager}.
     *
     * @author Christoph Strobl
     * @author Mark Strobl
     * @author Kezhu Wang
     * @since 2.0
     */
    public static class CommnetRedisCacheManagerBuilder {

        private final RedisCacheWriter cacheWriter;
        private RedisCacheConfiguration defaultCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
        private final Map<String, RedisCacheConfiguration> initialCaches = new LinkedHashMap<>();
        private boolean enableTransactions;
        boolean allowInFlightCacheCreation = true;

        private CommnetRedisCacheManagerBuilder(RedisCacheWriter cacheWriter) {
            this.cacheWriter = cacheWriter;
        }

        /**
         * Entry point for builder style {@link RedisCacheManager} configuration.
         *
         * @param connectionFactory must not be {@literal null}.
         * @return new {@link RedisCacheManager.RedisCacheManagerBuilder}.
         */
        public static CommnetRedisCacheManagerBuilder fromConnectionFactory(RedisConnectionFactory connectionFactory) {

            Assert.notNull(connectionFactory, "ConnectionFactory must not be null!");

            return commnetBuilder(new CommnetRedisCacheWriter(connectionFactory));
        }

        /**
         * Entry point for builder style {@link RedisCacheManager} configuration.
         *
         * @param cacheWriter must not be {@literal null}.
         * @return new {@link RedisCacheManager.RedisCacheManagerBuilder}.
         */
        public static CommnetRedisCacheManagerBuilder fromCacheWriter(RedisCacheWriter cacheWriter) {

            Assert.notNull(cacheWriter, "CacheWriter must not be null!");

            return new CommnetRedisCacheManagerBuilder(cacheWriter);
        }

        /**
         * Define a default {@link RedisCacheConfiguration} applied to dynamically created {@link RedisCache}s.
         *
         * @param defaultCacheConfiguration must not be {@literal null}.
         * @return this {@link RedisCacheManager.RedisCacheManagerBuilder}.
         */
        public CommnetRedisCacheManagerBuilder cacheDefaults(RedisCacheConfiguration defaultCacheConfiguration) {

            Assert.notNull(defaultCacheConfiguration, "DefaultCacheConfiguration must not be null!");

            this.defaultCacheConfiguration = defaultCacheConfiguration;

            return this;
        }

        /**
         * Enable {@link RedisCache}s to synchronize cache put/evict operations with ongoing Spring-managed transactions.
         *
         * @return this {@link RedisCacheManager.RedisCacheManagerBuilder}.
         */
        public CommnetRedisCacheManagerBuilder transactionAware() {

            this.enableTransactions = true;

            return this;
        }

        /**
         * Append a {@link Set} of cache names to be pre initialized with current {@link RedisCacheConfiguration}.
         * <strong>NOTE:</strong> This calls depends on {@link #cacheDefaults(RedisCacheConfiguration)} using whatever
         * default {@link RedisCacheConfiguration} is present at the time of invoking this method.
         *
         * @param cacheNames must not be {@literal null}.
         * @return this {@link RedisCacheManager.RedisCacheManagerBuilder}.
         */
        public CommnetRedisCacheManagerBuilder initialCacheNames(Set<String> cacheNames) {

            Assert.notNull(cacheNames, "CacheNames must not be null!");

            Map<String, RedisCacheConfiguration> cacheConfigMap = new LinkedHashMap<>(cacheNames.size());
            cacheNames.forEach(it -> cacheConfigMap.put(it, defaultCacheConfiguration));

            return withInitialCacheConfigurations(cacheConfigMap);
        }

        /**
         * Append a {@link Map} of cache name/{@link RedisCacheConfiguration} pairs to be pre initialized.
         *
         * @param cacheConfigurations must not be {@literal null}.
         * @return this {@link RedisCacheManager.RedisCacheManagerBuilder}.
         */
        public CommnetRedisCacheManagerBuilder withInitialCacheConfigurations(
                Map<String, RedisCacheConfiguration> cacheConfigurations) {

            Assert.notNull(cacheConfigurations, "CacheConfigurations must not be null!");
            cacheConfigurations.forEach((cacheName, configuration) -> Assert.notNull(configuration,
                    String.format("RedisCacheConfiguration for cache %s must not be null!", cacheName)));

            this.initialCaches.putAll(cacheConfigurations);

            return this;
        }

        /**
         * Disable in-flight {@link org.springframework.cache.Cache} creation for unconfigured caches.
         * <p/>
         * {@link RedisCacheManager#getMissingCache(String)} returns {@literal null} for any unconfigured
         * {@link org.springframework.cache.Cache} instead of a new {@link RedisCache} instance. This allows eg.
         * {@link org.springframework.cache.support.CompositeCacheManager} to chime in.
         *
         * @return this {@link RedisCacheManager.RedisCacheManagerBuilder}.
         * @since 2.0.4
         */
        public CommnetRedisCacheManagerBuilder disableCreateOnMissingCache() {

            this.allowInFlightCacheCreation = false;
            return this;
        }

        /**
         * Create new instance of {@link RedisCacheManager} with configuration options applied.
         *
         * @return new instance of {@link RedisCacheManager}.
         */
        public RedisCacheManager build() {

            CommnetRedisCacheManager cm = new CommnetRedisCacheManager(cacheWriter, defaultCacheConfiguration, initialCaches, allowInFlightCacheCreation);

            cm.setTransactionAware(enableTransactions);

            return cm;
        }
    }

}
java

import org.springframework.dao.PessimisticLockingFailureException;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;

import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Collections;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;

/**
 *  代码完全依照{org.springframework.data.redis.cache.DefaultRedisCacheWriter}
 * @author Brack.zhu
 * @date 2020/4/8
 */
public class CommnetRedisCacheWriter implements RedisCacheWriter {

    private final RedisConnectionFactory connectionFactory;
    private final Duration sleepTime;

    /**
     * @param connectionFactory must not be {@literal null}.
     */
    CommnetRedisCacheWriter(RedisConnectionFactory connectionFactory) {
        this(connectionFactory, Duration.ZERO);
    }

    /**
     * @param connectionFactory must not be {@literal null}.
     * @param sleepTime sleep time between lock request attempts. Must not be {@literal null}. Use {@link Duration#ZERO}
     *          to disable locking.
     */
    CommnetRedisCacheWriter(RedisConnectionFactory connectionFactory, Duration sleepTime) {

        Assert.notNull(connectionFactory, "ConnectionFactory must not be null!");
        Assert.notNull(sleepTime, "SleepTime must not be null!");

        this.connectionFactory = connectionFactory;
        this.sleepTime = sleepTime;
    }

    /*
     * (non-Javadoc)
     * @see org.springframework.data.redis.cache.RedisCacheWriter#put(java.lang.String, byte[], byte[], java.time.Duration)
     */
    @Override
    public void put(String name, byte[] key, byte[] value, @Nullable Duration ttl) {

        Assert.notNull(name, "Name must not be null!");
        Assert.notNull(key, "Key must not be null!");
        Assert.notNull(value, "Value must not be null!");

        execute(name, connection -> {

            if (shouldExpireWithin(ttl)) {
                connection.set(key, value, Expiration.from(ttl.toMillis(), TimeUnit.MILLISECONDS), RedisStringCommands.SetOption.upsert());
            } else {
                connection.set(key, value);
            }

            return "OK";
        });
    }

    /*
     * (non-Javadoc)
     * @see org.springframework.data.redis.cache.RedisCacheWriter#get(java.lang.String, byte[])
     */
    @Override
    public byte[] get(String name, byte[] key) {

        Assert.notNull(name, "Name must not be null!");
        Assert.notNull(key, "Key must not be null!");

        return execute(name, connection -> connection.get(key));
    }

    /*
     * (non-Javadoc)
     * @see org.springframework.data.redis.cache.RedisCacheWriter#putIfAbsent(java.lang.String, byte[], byte[], java.time.Duration)
     */
    @Override
    public byte[] putIfAbsent(String name, byte[] key, byte[] value, @Nullable Duration ttl) {

        Assert.notNull(name, "Name must not be null!");
        Assert.notNull(key, "Key must not be null!");
        Assert.notNull(value, "Value must not be null!");

        return execute(name, connection -> {

            if (isLockingCacheWriter()) {
                doLock(name, connection);
            }

            try {
                if (connection.setNX(key, value)) {

                    if (shouldExpireWithin(ttl)) {
                        connection.pExpire(key, ttl.toMillis());
                    }
                    return null;
                }

                return connection.get(key);
            } finally {

                if (isLockingCacheWriter()) {
                    doUnlock(name, connection);
                }
            }
        });
    }

    /*
     * (non-Javadoc)
     * @see org.springframework.data.redis.cache.RedisCacheWriter#remove(java.lang.String, byte[])
     */
    @Override
    public void remove(String name, byte[] key) {

        Assert.notNull(name, "Name must not be null!");
        Assert.notNull(key, "Key must not be null!");

        execute(name, connection -> connection.del(key));
    }

    /*
     * (non-Javadoc)
     * @see org.springframework.data.redis.cache.RedisCacheWriter#clean(java.lang.String, byte[])
     */
    @Override
    public void clean(String name, byte[] pattern) {

        Assert.notNull(name, "Name must not be null!");
        Assert.notNull(pattern, "Pattern must not be null!");

        execute(name, connection -> {

            boolean wasLocked = false;

            try {

                if (isLockingCacheWriter()) {
                    doLock(name, connection);
                    wasLocked = true;
                }

                byte[][] keys = Optional.ofNullable(connection.keys(pattern)).orElse(Collections.emptySet())
                        .toArray(new byte[0][]);

                if (keys.length > 0) {
                    connection.del(keys);
                }
            } finally {

                if (wasLocked && isLockingCacheWriter()) {
                    doUnlock(name, connection);
                }
            }

            return "OK";
        });
    }

    /**
     * Explicitly set a write lock on a cache.
     *
     * @param name the name of the cache to lock.
     */
    void lock(String name) {
        execute(name, connection -> doLock(name, connection));
    }

    /**
     * Explicitly remove a write lock from a cache.
     *
     * @param name the name of the cache to unlock.
     */
    void unlock(String name) {
        executeLockFree(connection -> doUnlock(name, connection));
    }

    private Boolean doLock(String name, RedisConnection connection) {
        return connection.setNX(createCacheLockKey(name), new byte[0]);
    }

    private Long doUnlock(String name, RedisConnection connection) {
        return connection.del(createCacheLockKey(name));
    }

    boolean doCheckLock(String name, RedisConnection connection) {
        return connection.exists(createCacheLockKey(name));
    }

    /**
     * @return {@literal true} if {@link RedisCacheWriter} uses locks.
     */
    private boolean isLockingCacheWriter() {
        return !sleepTime.isZero() && !sleepTime.isNegative();
    }

    private <T> T execute(String name, Function<RedisConnection, T> callback) {

        RedisConnection connection = connectionFactory.getConnection();
        try {

            checkAndPotentiallyWaitUntilUnlocked(name, connection);
            return callback.apply(connection);
        } finally {
            connection.close();
        }
    }

    private void executeLockFree(Consumer<RedisConnection> callback) {

        RedisConnection connection = connectionFactory.getConnection();

        try {
            callback.accept(connection);
        } finally {
            connection.close();
        }
    }

    private void checkAndPotentiallyWaitUntilUnlocked(String name, RedisConnection connection) {

        if (!isLockingCacheWriter()) {
            return;
        }

        try {

            while (doCheckLock(name, connection)) {
                Thread.sleep(sleepTime.toMillis());
            }
        } catch (InterruptedException ex) {

            // Re-interrupt current thread, to allow other participants to react.
            Thread.currentThread().interrupt();

            throw new PessimisticLockingFailureException(String.format("Interrupted while waiting to unlock cache %s", name),
                    ex);
        }
    }

    private static boolean shouldExpireWithin(@Nullable Duration ttl) {
        return ttl != null && !ttl.isZero() && !ttl.isNegative();
    }

    private static byte[] createCacheLockKey(String name) {
        return (name + "~lock").getBytes(StandardCharsets.UTF_8);
    }

}

自定义缓存配置

java

import com.commnetsoft.core.cache.extend.CommnetCacheInterceptor;
import com.commnetsoft.core.cache.extend.annotation.CommnetSpringCacheAnnotationParser;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.cache.annotation.AnnotationCacheOperationSource;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.annotation.ProxyCachingConfiguration;
import org.springframework.cache.config.CacheManagementConfigUtils;
import org.springframework.cache.interceptor.BeanFactoryCacheOperationSourceAdvisor;
import org.springframework.cache.interceptor.CacheInterceptor;
import org.springframework.cache.interceptor.CacheOperationSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Role;

/**
 * 自定义缓存配置----只支持Redis
 * @author Brack.zhu
 * @date 2020/3/25
 */
@EnableCaching
@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
//@Primary
public class CommnetProxyCachingConfiguration extends ProxyCachingConfiguration {

    @Value("#{coreConfig.applicationName}")
    private String applicationName;

    @Override
    @Bean(name = CacheManagementConfigUtils.CACHE_ADVISOR_BEAN_NAME)
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public BeanFactoryCacheOperationSourceAdvisor cacheAdvisor() {
        BeanFactoryCacheOperationSourceAdvisor advisor = new BeanFactoryCacheOperationSourceAdvisor();
        advisor.setCacheOperationSource(cacheOperationSource());
        advisor.setAdvice(cacheInterceptor());
        if (this.enableCaching != null) {
            advisor.setOrder(this.enableCaching.<Integer>getNumber("order"));
        }
        return advisor;
    }

    @Override
    @Bean
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public CacheInterceptor cacheInterceptor() {
        //自定义缓存拦截器
        CacheInterceptor interceptor = new CommnetCacheInterceptor();
        interceptor.configure(this.errorHandler, this.keyGenerator, this.cacheResolver, this.cacheManager);
        interceptor.setCacheOperationSource(cacheOperationSource());
        return interceptor;
    }

    @Override
    @Bean
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public CacheOperationSource cacheOperationSource() {
        //自定义缓存注解解析器;支持cacheNames\value 自动扩展模块名称。格式:{模块名}_注解value
        CommnetSpringCacheAnnotationParser commnetSpringCacheAnnotationParser=new CommnetSpringCacheAnnotationParser(applicationName);
        return new AnnotationCacheOperationSource(commnetSpringCacheAnnotationParser);
    }


}

缓存拦截器(拦截注解配置超时)

java

import java.lang.annotation.*;

/**
 * 缓存超时 注解<br/>
 *  需要配合Spring cache 注解使用
 * @author Brack.zhu
 * @date 2020/4/8
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CacheDuration {

    /**
     * 最大存活时间,使用常量参数;默认:-1(永久),单位毫秒
     * @return
     */
    long value() default -1;

    /**
     * 最大存活时间,使用变量参数,使用Spring的表达式,SpEL 如:#{systemProperties.myProp}<br/>
     * 表达式返回值类型long,单位毫秒,
     *
     * @return
     */
    String var() default "";

}




import com.commnetsoft.commons.utils.StringUtils;
import com.commnetsoft.core.cache.CacheDuration;
import com.commnetsoft.core.utils.SpElUtil;
import com.commnetsoft.core.utils.SpringContextUtil;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.cache.CacheManager;
import org.springframework.cache.interceptor.CacheInterceptor;



/**
 * 自定义缓存拦截器
 *
 * @author Brack.zhu
 * @date 2020/3/25
 */
@SuppressWarnings("serial")
public class CommnetCacheInterceptor extends CacheInterceptor {

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        //扩展---支持缓存单独设置存活时间
        boolean clear = false;
        try {
            if (isSupport()) {
                //设置单个缓存超时时间
                CacheDuration cacheDuration = invocation.getMethod().getAnnotation(CacheDuration.class);
                if (null != cacheDuration) {
                    long duration = 0;
                    if (cacheDuration.value() > 0) {
                        //指定常量值
                        duration = cacheDuration.value();
                    } else if (StringUtils.isNotBlank(cacheDuration.var())) {
                        //使用参数变量值
                        //使用SPEL进行var的解析
                        duration=SpElUtil.evaluate(cacheDuration.var());
                    }
                    if (duration > 0) {
                        clear = true;
                        CommnetRedisCacheManager.addCacheDuration(duration);
                    }
                }
            }
            return super.invoke(invocation);
        } finally {
            if (clear) {
                //清除单个缓存超时时间
                CommnetRedisCacheManager.clearCacheDuration();
            }
        }
    }

    private Boolean isSupportCacheType = null;

    /**
     * 是否是支持的缓存类型
     *
     * @return false不支持
     */
    public boolean isSupport() {
        if (null == isSupportCacheType) {
            CacheManager cacheManager = SpringContextUtil.getBean(CacheManager.class);
            if (cacheManager instanceof CommnetRedisCacheManager) {
                isSupportCacheType = true;
            } else {
                isSupportCacheType = false;
            }
        }
        return isSupportCacheType;
    }

}

缓存注解解析器扩展

java

import com.commnetsoft.core.CoreConstant;
import org.springframework.cache.annotation.*;
import org.springframework.cache.interceptor.CacheEvictOperation;
import org.springframework.cache.interceptor.CacheOperation;
import org.springframework.cache.interceptor.CachePutOperation;
import org.springframework.cache.interceptor.CacheableOperation;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;

import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Set;

/**
 * Spring缓存注解解析器扩展<br>
 *  修改点:1:支持cacheNames\value 自动扩展模块名称。格式:{模块名}_注解value <br/>
 *         2:自动将SpringCache注解返回类增加到FastJson序列化白名单中
 *   参考实现 {@link org.springframework.cache.annotation.SpringCacheAnnotationParser}
 * @author Brack.zhu
 * @date 2020/5/12
 */
public class CommnetSpringCacheAnnotationParser  implements CacheAnnotationParser, Serializable {

    private String applicationName;

    public CommnetSpringCacheAnnotationParser(String applicationName){
        this.applicationName=applicationName;
    }

    private static final Set<Class<? extends Annotation>> CACHE_OPERATION_ANNOTATIONS = new LinkedHashSet<>(8);

    /**
     * 扩展---cachename 间隔符
     */
    public static final String CacheNameMark= CoreConstant.Cache.REDIS_KEY_MARK;

    static {
        CACHE_OPERATION_ANNOTATIONS.add(Cacheable.class);
        CACHE_OPERATION_ANNOTATIONS.add(CacheEvict.class);
        CACHE_OPERATION_ANNOTATIONS.add(CachePut.class);
        CACHE_OPERATION_ANNOTATIONS.add(Caching.class);
    }


    @Override
    @Nullable
    public Collection<CacheOperation> parseCacheAnnotations(Class<?> type) {
        CommnetSpringCacheAnnotationParser.DefaultCacheConfig defaultConfig = new CommnetSpringCacheAnnotationParser.DefaultCacheConfig(type);
        return parseCacheAnnotations(defaultConfig, type);
    }

    @Override
    @Nullable
    public Collection<CacheOperation> parseCacheAnnotations(Method method) {
        CommnetSpringCacheAnnotationParser.DefaultCacheConfig defaultConfig = new CommnetSpringCacheAnnotationParser.DefaultCacheConfig(method.getDeclaringClass());
        return parseCacheAnnotations(defaultConfig, method);
    }

    @Nullable
    private Collection<CacheOperation> parseCacheAnnotations(CommnetSpringCacheAnnotationParser.DefaultCacheConfig cachingConfig, AnnotatedElement ae) {
        Collection<CacheOperation> ops = parseCacheAnnotations(cachingConfig, ae, false);
        if (ops != null && ops.size() > 1) {
            // More than one operation found -> local declarations override interface-declared ones...
            Collection<CacheOperation> localOps = parseCacheAnnotations(cachingConfig, ae, true);
            if (localOps != null) {
                return localOps;
            }
        }
        return ops;
    }

    @Nullable
    private Collection<CacheOperation> parseCacheAnnotations(
            CommnetSpringCacheAnnotationParser.DefaultCacheConfig cachingConfig, AnnotatedElement ae, boolean localOnly) {

        Collection<? extends Annotation> anns = (localOnly ?
                AnnotatedElementUtils.getAllMergedAnnotations(ae, CACHE_OPERATION_ANNOTATIONS) :
                AnnotatedElementUtils.findAllMergedAnnotations(ae, CACHE_OPERATION_ANNOTATIONS));
        if (anns.isEmpty()) {
            return null;
        }

        final Collection<CacheOperation> ops = new ArrayList<>(1);
        anns.stream().filter(ann -> ann instanceof Cacheable).forEach(
                ann -> ops.add(parseCacheableAnnotation(ae, cachingConfig, (Cacheable) ann)));
        anns.stream().filter(ann -> ann instanceof CacheEvict).forEach(
                ann -> ops.add(parseEvictAnnotation(ae, cachingConfig, (CacheEvict) ann)));
        anns.stream().filter(ann -> ann instanceof CachePut).forEach(
                ann -> ops.add(parsePutAnnotation(ae, cachingConfig, (CachePut) ann)));
        anns.stream().filter(ann -> ann instanceof Caching).forEach(
                ann -> parseCachingAnnotation(ae, cachingConfig, (Caching) ann, ops));
        return ops;
    }

    private CacheableOperation parseCacheableAnnotation(
            AnnotatedElement ae, CommnetSpringCacheAnnotationParser.DefaultCacheConfig defaultConfig, Cacheable cacheable) {
        //--扩展
        fastjsonAccept(ae);

        CacheableOperation.Builder builder = new CacheableOperation.Builder();
        builder.setName(ae.toString());
        //--扩展
        builder.setCacheNames(extendCacheName(cacheable.cacheNames()));
        builder.setCondition(cacheable.condition());
        builder.setUnless(cacheable.unless());
        builder.setKey(cacheable.key());
        builder.setKeyGenerator(cacheable.keyGenerator());
        builder.setCacheManager(cacheable.cacheManager());
        builder.setCacheResolver(cacheable.cacheResolver());
        builder.setSync(cacheable.sync());

        defaultConfig.applyDefault(builder);
        CacheableOperation op = builder.build();
        validateCacheOperation(ae, op);

        return op;
    }

    private CacheEvictOperation parseEvictAnnotation(
            AnnotatedElement ae, CommnetSpringCacheAnnotationParser.DefaultCacheConfig defaultConfig, CacheEvict cacheEvict) {
        //--扩展
        fastjsonAccept(ae);

        CacheEvictOperation.Builder builder = new CacheEvictOperation.Builder();

        builder.setName(ae.toString());
        //--扩展
        builder.setCacheNames(extendCacheName(cacheEvict.cacheNames()));
        builder.setCondition(cacheEvict.condition());
        builder.setKey(cacheEvict.key());
        builder.setKeyGenerator(cacheEvict.keyGenerator());
        builder.setCacheManager(cacheEvict.cacheManager());
        builder.setCacheResolver(cacheEvict.cacheResolver());
        builder.setCacheWide(cacheEvict.allEntries());
        builder.setBeforeInvocation(cacheEvict.beforeInvocation());

        defaultConfig.applyDefault(builder);
        CacheEvictOperation op = builder.build();
        validateCacheOperation(ae, op);

        return op;
    }

    private CacheOperation parsePutAnnotation(
            AnnotatedElement ae, CommnetSpringCacheAnnotationParser.DefaultCacheConfig defaultConfig, CachePut cachePut) {
        //--扩展
        fastjsonAccept(ae);

        CachePutOperation.Builder builder = new CachePutOperation.Builder();

        builder.setName(ae.toString());
        //--扩展
        builder.setCacheNames(extendCacheName(cachePut.cacheNames()));
        builder.setCondition(cachePut.condition());
        builder.setUnless(cachePut.unless());
        builder.setKey(cachePut.key());
        builder.setKeyGenerator(cachePut.keyGenerator());
        builder.setCacheManager(cachePut.cacheManager());
        builder.setCacheResolver(cachePut.cacheResolver());

        defaultConfig.applyDefault(builder);
        CachePutOperation op = builder.build();
        validateCacheOperation(ae, op);

        return op;
    }

    private void parseCachingAnnotation(
            AnnotatedElement ae, CommnetSpringCacheAnnotationParser.DefaultCacheConfig defaultConfig, Caching caching, Collection<CacheOperation> ops) {

        Cacheable[] cacheables = caching.cacheable();
        for (Cacheable cacheable : cacheables) {
            ops.add(parseCacheableAnnotation(ae, defaultConfig, cacheable));
        }
        CacheEvict[] cacheEvicts = caching.evict();
        for (CacheEvict cacheEvict : cacheEvicts) {
            ops.add(parseEvictAnnotation(ae, defaultConfig, cacheEvict));
        }
        CachePut[] cachePuts = caching.put();
        for (CachePut cachePut : cachePuts) {
            ops.add(parsePutAnnotation(ae, defaultConfig, cachePut));
        }
    }


    /**
     * Validates the specified {@link CacheOperation}.
     * <p>Throws an {@link IllegalStateException} if the state of the operation is
     * invalid. As there might be multiple sources for default values, this ensure
     * that the operation is in a proper state before being returned.
     * @param ae the annotated element of the cache operation
     * @param operation the {@link CacheOperation} to validate
     */
    private void validateCacheOperation(AnnotatedElement ae, CacheOperation operation) {
        if (StringUtils.hasText(operation.getKey()) && StringUtils.hasText(operation.getKeyGenerator())) {
            throw new IllegalStateException("Invalid cache annotation configuration on '" +
                    ae.toString() + "'. Both 'key' and 'keyGenerator' attributes have been set. " +
                    "These attributes are mutually exclusive: either set the SpEL expression used to" +
                    "compute the key at runtime or set the name of the KeyGenerator bean to use.");
        }
        if (StringUtils.hasText(operation.getCacheManager()) && StringUtils.hasText(operation.getCacheResolver())) {
            throw new IllegalStateException("Invalid cache annotation configuration on '" +
                    ae.toString() + "'. Both 'cacheManager' and 'cacheResolver' attributes have been set. " +
                    "These attributes are mutually exclusive: the cache manager is used to configure a" +
                    "default cache resolver if none is set. If a cache resolver is set, the cache manager" +
                    "won't be used.");
        }
    }

    @Override
    public boolean equals(Object other) {
        return (this == other || other instanceof SpringCacheAnnotationParser);
    }

    @Override
    public int hashCode() {
        return SpringCacheAnnotationParser.class.hashCode();
    }

    /**
     * 扩展---cachename缓存加上模块前缀
     * @param baseNames
     * @return
     */
    public String[] extendCacheName(String... baseNames){
        String[] extendBaseNames=new String[baseNames.length];
        for(int i=0;i<baseNames.length;i++){
            String baseName=baseNames[i];
            extendBaseNames[i]=applicationName+CacheNameMark+baseName;
        }
        return extendBaseNames;
    }

    /**
     * 扩展---为fastJson 反序列化创建类白名单
     * @param ae
     */
    public void fastjsonAccept(AnnotatedElement ae){
        com.commnetsoft.core.cache.CacheConfig.fastjsonAddAccept(ae);
    }




    /**
     * Provides default settings for a given set of cache operations.
     */
    private static class DefaultCacheConfig {

        private final Class<?> target;

        @Nullable
        private String[] cacheNames;

        @Nullable
        private String keyGenerator;

        @Nullable
        private String cacheManager;

        @Nullable
        private String cacheResolver;

        private boolean initialized = false;

        public DefaultCacheConfig(Class<?> target) {
            this.target = target;
        }

        /**
         * Apply the defaults to the specified {@link CacheOperation.Builder}.
         * @param builder the operation builder to update
         */
        public void applyDefault(CacheOperation.Builder builder) {
            if (!this.initialized) {
                CacheConfig annotation = AnnotatedElementUtils.findMergedAnnotation(this.target, CacheConfig.class);
                if (annotation != null) {
                    this.cacheNames = annotation.cacheNames();
                    this.keyGenerator = annotation.keyGenerator();
                    this.cacheManager = annotation.cacheManager();
                    this.cacheResolver = annotation.cacheResolver();
                }
                this.initialized = true;
            }

            if (builder.getCacheNames().isEmpty() && this.cacheNames != null) {
                builder.setCacheNames(this.cacheNames);
            }
            if (!StringUtils.hasText(builder.getKey()) && !StringUtils.hasText(builder.getKeyGenerator()) &&
                    StringUtils.hasText(this.keyGenerator)) {
                builder.setKeyGenerator(this.keyGenerator);
            }

            if (StringUtils.hasText(builder.getCacheManager()) || StringUtils.hasText(builder.getCacheResolver())) {
                // One of these is set so we should not inherit anything
            }
            else if (StringUtils.hasText(this.cacheResolver)) {
                builder.setCacheResolver(this.cacheResolver);
            }
            else if (StringUtils.hasText(this.cacheManager)) {
                builder.setCacheManager(this.cacheManager);
            }
        }
    }

}

二、Guava实现缓存

参考guava